package com.rayo.accesscontrollercopy.model.network;

import android.content.Context;
import android.os.Handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.litesuits.android.log.Log;
import com.rayo.accesscontrollercopy.R;
import com.rayo.accesscontrollercopy.config.GlobalVariables;
import com.rayo.accesscontrollercopy.model.callback.ApiCallback;
import com.rayo.accesscontrollercopy.model.callback.OnNetworkListener;
import com.rayo.accesscontrollercopy.model.db.bean.GetDbParam;
import com.rayo.accesscontrollercopy.model.db.bean.HeartData;
import com.rayo.accesscontrollercopy.model.db.bean.SystemData;
import com.rayo.accesscontrollercopy.model.db.bean.UpdateApp;
import com.rayo.accesscontrollercopy.model.db.dao.CalendarImpl;
import com.rayo.accesscontrollercopy.model.db.dao.CardTimeImpl;
import com.rayo.accesscontrollercopy.model.db.dao.CardUserImpl;
import com.rayo.accesscontrollercopy.model.db.dao.DSTImpl;
import com.rayo.accesscontrollercopy.model.db.dao.HeartDao;
import com.rayo.accesscontrollercopy.model.db.dao.HeartImpl;
import com.rayo.accesscontrollercopy.model.db.dao.KeyTimeImpl;
import com.rayo.accesscontrollercopy.model.db.dao.KeyUserImpl;
import com.rayo.accesscontrollercopy.model.db.dao.OperatorImpl;
import com.rayo.accesscontrollercopy.model.db.dao.PlanKeyAddDao;
import com.rayo.accesscontrollercopy.model.db.dao.PlanKeyAddImpl;
import com.rayo.accesscontrollercopy.model.db.dao.PlanKeyDelDao;
import com.rayo.accesscontrollercopy.model.db.dao.PlanKeyDelImpl;
import com.rayo.accesscontrollercopy.model.db.dao.PlanKeyTempImpl;
import com.rayo.accesscontrollercopy.model.network.bean.BaseApi;
import com.rayo.accesscontrollercopy.model.network.bean.ConnectApi;
import com.rayo.accesscontrollercopy.model.network.bean.HeartApi;
import com.rayo.accesscontrollercopy.model.network.bean.UpdatedUserApi;
import com.rayo.accesscontrollercopy.model.service.NetWorkService;
import com.rayo.accesscontrollercopy.utils.AppUtils;
import com.rayo.accesscontrollercopy.utils.FileUtils;
import com.rayo.wallreader.utils.TimeUtils;

import java.io.File;
import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;


/**
 * Created by yanghaojie on 2017/2/8.
 */

public class WebSocketHelp {
    private static final String TAG = "webSocketHelp";
    private static WebSocketHelp mInstance;
    private OkHttpClient mOkHttpClient;
    private boolean isConnect;
    private Request mRequest;
    private ExecutorService writeExecutor = null;
    private ApiCallback mApiCallback;
    private OnNetworkListener mOnNetworkListener;
    //    private AnalysisData mAnalysisData;
    private static String mUrl;
    private String appVersion;
    //    private WebSocketCall mWebSocketCall;
    private WebSocket mWebSocket;
    private final static int TIMEOUT = 10;
    private long size = 0L;
    private long current = 0L;
    public final static int ConnectException = 0;
    public final static int OrtherException = 1;
    private final static int CANCEL_CODE=1009;
    private final static int CLOSE_CODE=1008;
    private final static int CLOSE_CODE_SERVER=1000;
    private boolean autoIP = false;
    private Handler mHandler=new Handler();
    private WebSocketHelp() {
        isConnect = false;
        mWebSocket = null;
        mApiCallback = null;
        mOnNetworkListener = null;
        appVersion = "";
//        mAnalysisData = new AnalysisData();
        mUrl = "";
        initHttp();

    }

    public static WebSocketHelp getmInstance() {
        if (mInstance == null) {
            synchronized (WebSocketHelp.class) {
                if (mInstance == null) {
                    mInstance = new WebSocketHelp();
                }
            }
        }
        return mInstance;
    }

    public void init(ApiCallback apiCallback, OnNetworkListener onNetworkListener) {
        this.mApiCallback = apiCallback;
        this.mOnNetworkListener = onNetworkListener;
    }

    public boolean isConnect() {
        return isConnect && (null != mWebSocket);
    }

    private void initHttp() {
        mOkHttpClient = new OkHttpClient.Builder().readTimeout(TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(TIMEOUT, TimeUnit.SECONDS)
                .connectTimeout(TIMEOUT, TimeUnit.SECONDS).sslSocketFactory(setSSL()).hostnameVerifier(verifier).build();
    }

    HostnameVerifier verifier = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };

    public void connect() {
//        if (!isConnect || null == mWebSocket) {
        if (null == mUrl || mUrl.equals(":") || isConnect()) return;

        String[]ip=mUrl.split(":");
        if(ip.length!=2){
            return;
        }
        else {
            if(Integer.parseInt(ip[1])>65535){
                mApiCallback.onError((byte)-4,"");
                return;
            }
        }
        mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_CONNECTING,"");
        Log.e(TAG, "try to connect");
        mRequest = new Request.Builder().url("wss:" + mUrl + "/").build();
        if (mWebSocket != null) {
            Log.e(TAG, "cancel");
            mWebSocket.cancel();
        }
        mWebSocket = mOkHttpClient.newWebSocket(mRequest, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                Log.e(TAG, "open");
                if (!autoIP) {
                    SystemData systemData = GlobalVariables.getInstance().getmSystemData();
                    if (systemData != null) {
                        try {
                            autoIP = true;
                            String[] strings = mUrl.split(":");
                            if (strings.length == 2) {
                                systemData.setServerIp(strings[0]);
                                systemData.setServerPort(strings[1]);
                                GlobalVariables.getInstance().setmSystemData(systemData);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                writeExecutor = Executors.newSingleThreadExecutor();
                isConnect = true;
                mWebSocket = webSocket;

                Log.d(TAG, NetWorkService.ACTION_NETWORK_CONNECT);
                mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_CONNECT, null);
                sendMessage(JSON.toJSONString(new BaseApi(ApiCmd.CONNECT, GlobalVariables.getInstance().getmSiteData().getSite(), new ConnectApi())));
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                 mHandler.removeCallbacksAndMessages(null);
                Log.e(TAG, "read:" + text);
                BaseApi mBaseApi = new BaseApi();
                JSONObject mJSONObject = null;
                String data = null;
                try {
                    mJSONObject = JSON.parseObject(text);
                    mBaseApi.setCmd(mJSONObject.getInteger(BaseApi.CMD));
                    mBaseApi.setSite(mJSONObject.getString(BaseApi.SITE));
                    mBaseApi.setData(mJSONObject.get(BaseApi.DATA));
                    data =  mJSONObject.getString(BaseApi.DATA);
                } catch (Exception e) {
                    e.printStackTrace();
                    mBaseApi = null;
                    mJSONObject = null;
                }
                if (null == mBaseApi || mJSONObject == null) {
                    mApiCallback.onFailure();
                    return;
                }
                if (GlobalVariables.getInstance().getmSiteData().getSite().equals("0"))
                    GlobalVariables.getInstance().getmSiteData().setSite(mBaseApi.getSite());
                if (data.contains("error")){
                    mApiCallback.onError((byte) -1, mJSONObject.getString(BaseApi.DATA));
                } else {
                    mApiCallback.onSuccess((byte) mBaseApi.getCmd(), mJSONObject.getJSONObject(BaseApi.DATA));
                }
                if (mBaseApi.getCmd() == ApiCmd.UPDATE_APP) {
                    UpdateApp updateApp = JSON.parseObject(mJSONObject.getJSONObject(BaseApi.DATA).toJSONString(), UpdateApp.class);
                    String path = FileUtils.getSDPath() + WebSocketHelp.getmInstance().getAppVersion() + ".apk";
                    android.util.Log.e("delete", FileUtils.deleteFile(path) + "");
                    size = Long.valueOf(updateApp.getSize());
                    current = 0L;
                    android.util.Log.e("size", size + "");
                }
//                    mAnalysisData.analysis(data, mApiCallback);
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                mHandler.removeCallbacksAndMessages(null);
                byte[] source = bytes.toByteArray();
                current = current + source.length;
                android.util.Log.e("loadapp", current + "");
                File file = FileUtils.saveFile(source, WebSocketHelp.getmInstance().getAppVersion() + ".apk");

                if (null != file) {
                    android.util.Log.e("load", file.length() + "");
                    if (null != mOnNetworkListener) {
                        if (current == size)
                            mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_UPDATE_APP, file);
                    }
                } else {
                    if (null != mApiCallback)
                        mApiCallback.onError((byte) -2, "Save or download APP error");
                }
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                Log.e("closing", "closeCode" + code + "" + reason);
                mHandler.removeCallbacksAndMessages(null);
                isConnect = false;
                mWebSocket = null;
                writeExecutor.shutdown();
//                Log.d(TAG, NetWorkService.ACTION_NETWORK_DISCONNECT);
                if (code == CLOSE_CODE_SERVER) {
                    connect();
                    mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_DISCONNECT, null);
                }
                else if ((code==CANCEL_CODE)){
//                    closeExecutor.shutdown();
                    connect();
                }
                else {
                    mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_DISCONNECT, null);
                }
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                Log.e("close", "closeCode" + code + ""+reason);
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable e, @Nullable Response response) {
                Log.e("failed", "failed");
                mHandler.removeCallbacksAndMessages(null);
                if (e instanceof java.net.ConnectException) {
                    mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_FAILED, ConnectException);
                    isConnect = false;
                } else if (e instanceof SocketException) {
                    if (e.getMessage().equals("Socket closed")) {

//                        if(clientClosed){
//                           clientClosed=false;
//                        }
//                        else {
//                            mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_FAILED, OrtherException);
//                        }
                    } else {
                        isConnect = false;
                        mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_FAILED, OrtherException);
                    }
                    Log.e(TAG, "socketfailed" + JSON.toJSONString(e));
                } else {
                    isConnect = false;
                    mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_FAILED, OrtherException);
                }
                Log.e(TAG, e.toString());
//                mWebSocket = null;
                Log.d(TAG, NetWorkService.ACTION_NETWORK_FAILED);
            }
        });
    }

    public void sendMessage(final String data) {
        if (!isConnect()) {
            mApiCallback.onError((byte) -1, "disconnect");
            return;
        }
        writeExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (mWebSocket != null) {
                    mWebSocket.send(data);
                    mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_SEND, data);
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            isConnect=false;
                          mOnNetworkListener.onTimeOut();
                        }
                    },TIMEOUT*1000);
                }
                Log.e(TAG, "send:" + data);

            }
        });
    }
    public void sendMessage(byte cmd,final String data) {
        if (!isConnect()) {
            mApiCallback.onError((byte) -1, "disconnect");
            return;
        }
        writeExecutor.execute(new Runnable() {
            @Override
            public void run() {

                if (mWebSocket != null) {
                    mWebSocket.send(data);
                    mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_SEND, data);
                }
                Log.e(TAG, "send:" + data);

            }
        });
    }

    public SSLSocketFactory setSSL() {
        X509TrustManager x509TrustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        try {
            sslContext.init(null, new TrustManager[]{x509TrustManager}, new SecureRandom());
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return sslContext.getSocketFactory();

    }

    public void closeWebsocket(final boolean exit) {
        mHandler.removeCallbacksAndMessages(null);
        if (isConnect) {
            isConnect = false;
            if(exit){
                mWebSocket.close(CLOSE_CODE, "Normal Close");
            }
            else {
                mWebSocket.close(CANCEL_CODE, "Normal Close");
            }
//
        }
        else {
            if (!exit) {
                connect();
            }
        }
    }

    public void setAppVersion(String appVersion) {
        this.appVersion = appVersion;
    }

    public String getAppVersion() {
        return appVersion;
    }

    public void setmUrl(String mUrl) {
        WebSocketHelp.mUrl = mUrl;
    }

    public void updateDb(String lockId, boolean clear) {
        if (clear) {
            new CalendarImpl().deleteAll();
            new CardTimeImpl().deleteAll();
            new CardUserImpl().deleteAll();
            new DSTImpl().delete();
            new HeartImpl().deleteAll();
            new KeyTimeImpl().deleteAll();
            new KeyUserImpl().deleteAll();
            new OperatorImpl().deleteAll();
            new PlanKeyAddImpl().deleteAll();
            new PlanKeyDelImpl().deleteAll();
            new PlanKeyTempImpl().deleteAll();
            new HeartImpl().deleteAll();
        }
        HeartDao heartDao = new HeartImpl();
        HeartData mHeartData = heartDao.findById(GlobalVariables.getInstance().getmSiteData().getSite(),
                Integer.valueOf(lockId));
        HeartApi heartApi = new HeartApi();
        heartApi.setLockid(lockId);
        if (null == mHeartData) {
            heartApi.setKeyuser(TimeUtils.dateFromNotYMDHMS("010102010100"));
            heartApi.setCarduser(TimeUtils.dateFromNotYMDHMS("010102010100"));
            heartApi.setLock(TimeUtils.dateFromNotYMDHMS("010102010100"));

        } else {
            heartApi.setKeyuser(mHeartData.getKeyusertime());
            heartApi.setCarduser(mHeartData.getCardusertime());
            heartApi.setLock(mHeartData.getLockusertime());
        }
        WebSocketHelp.getmInstance().sendMessage(JSON.toJSONString(new BaseApi(ApiCmd.HEART,
                GlobalVariables.getInstance().getmSiteData().getSite(), heartApi)));
//        WebSocketHelp.getmInstance().sendMessage(JSON.toJSONString(new BaseApi(ApiCmd.HEART,
//                GlobalVariables.getInstance().getmSiteData().getSite(), heartApi)), new ApiCallback() {
//            @Override
//            public <T> void onSuccess(byte cmd, T ret) {
//                switch (cmd) {
//                    case ApiCmd.HEART:
//                        break;
//                    case ApiCmd.UPDATE_ID:
//                        break;
//                    case ApiCmd.UPDATE_OPERATOR:
//                        break;
//                    case ApiCmd.UPDATE_KEY:
//                        break;
//                    case ApiCmd.UPDATE_CARD:
//                        if (null != mOnNetworkListener)
//                            mOnNetworkListener.onSuccess(NetWorkService.ACTION_NETWORK_UPDATE_DB, null);
//                        break;
//                    default:
//                        break;
//                }
//            }
//
//            @Override
//            public void onError(String err_msg) {
//                if (null != mOnNetworkListener)
//                    mOnNetworkListener.onFailed(NetWorkService.ACTION_NETWORK_UPDATE_DB, err_msg);
//            }
//
//            @Override
//            public void onFailure() {
//                if (null != mOnNetworkListener)
//                    mOnNetworkListener.onFailed(NetWorkService.ACTION_NETWORK_UPDATE_DB, "");
//            }
//        });
    }

    public void updateUserFinish(int userId, int type) {
        if (type == 4) {
            PlanKeyAddDao keyAddDao = new PlanKeyAddImpl();
            keyAddDao.deleteByUserId(userId);
            PlanKeyDelDao keyDelDao = new PlanKeyDelImpl();
            keyDelDao.deleteByUserId(userId);
        }
        UpdatedUserApi updatedUserApi = new UpdatedUserApi(String.valueOf(userId), String.valueOf(type), new Date());
        WebSocketHelp.getmInstance().sendMessage(JSON.toJSONString(new BaseApi(ApiCmd.UPDATED_USER,
                GlobalVariables.getInstance().getmSiteData().getSite(), updatedUserApi)));
    }

    public void updateApp(Context context) {
        if (AppUtils.isNewVersion(appVersion, context)) {
            BaseApi baseApi = new BaseApi(ApiCmd.UPDATE_APP,
                    GlobalVariables.getInstance().getmSiteData().getSite(), new Object());
            WebSocketHelp.getmInstance().sendMessage(JSON.toJSONString(baseApi));
        } else {
            mApiCallback.onError((byte) -3, context.getString(R.string.no_update));
        }
    }
    public void getDb(String code) {
        GetDbParam getDbParam = new GetDbParam();
        GetDbParam.DataBean dataBean = new GetDbParam.DataBean();
        dataBean.setCode(code);
        getDbParam.setCmd(25);
        getDbParam.setData(dataBean);
        getDbParam.setSite("1");
        SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
        String date = sdf.format(new java.util.Date());
        getDbParam.setTime(date);
        WebSocketHelp.getmInstance().sendMessage(ApiCmd.UPDATE_DB, JSON.toJSONString(getDbParam));
    }
}
